home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / X11 / wais / waisgate / irfiles.c < prev    next >
C/C++ Source or Header  |  1995-05-09  |  54KB  |  1,858 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE:
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.
  4.  
  5.    Brewster@think.com
  6. */
  7.  
  8. /* This file defines the files of an inverted file index.
  9.  *
  10.  * This structure is designed to be flexible rather than particularly
  11.  * optimized for speed or space.
  12.  * Thus this organization can support:
  13.  *   boolean, proximity, weights, and relevance feedback.
  14.  *
  15.  * Ported directly from the Lisp version 1.2 of the search engine.
  16.  *
  17.  * -brewster 6/90
  18.  */
  19.  
  20. #ifndef lint
  21. static char *RCSid = "$Header: /tmp_mnt/net/quake/proj/wais/wais-8-b5/ir/RCS/irfiles.c,v 1.63 92/04/28 16:54:41 morris Exp $";
  22. #endif
  23.  
  24. /* ==================== */
  25. /* ===  Change Log  === */
  26. /*Created 12/4/89 Brewster full lisp version
  27.  *split from ir-engine 1/11/90 brewster
  28.  *
  29.  *added memory indexing for efficiency
  30.  *added variable index block sizes
  31.  *5/90 ported to C
  32.  *5/90 split from irbuild.c
  33.  *7/90 declared truename() a static function - HWM 
  34.  *7/90 changed filename table and headline table to be null
  35.  *     terminated in the file rather than \newline.
  36.  *     compatibility problems between systems (sigh).
  37.  *     -brewster
  38.  *7/90 added field to document table for WAIStation
  39.  *     -brewster
  40.  *7/90 fixed: BUG: when adding words to the word disk hashtable, watch out 
  41.  *     for the end of the file and wrap.  If it is full, error out.
  42.  *3/91 took out utilities and created futil.c -brewster
  43.  *3/91 took out the inverted file and created irinv.c -brewster
  44.  *
  45.  * $Log:    irfiles.c,v $
  46.  * Revision 1.63  92/04/28  16:54:41  morris
  47.  * added boolean support
  48.  * 
  49.  * Revision 1.62  92/03/20  13:57:04  jonathan
  50.  * New and Improved server registration.
  51.  * 
  52.  * Revision 1.61  92/03/19  10:38:27  shen
  53.  * modified lock to prevent more than one indexing at the same time.
  54.  * modified lock to block query while initilaizing a database
  55.  * 
  56.  * Revision 1.60  92/03/19  09:33:35  morris
  57.  * fixed the dictionary header to accurately indicate the number of blocks
  58.  * 
  59.  * Revision 1.59  92/02/27  12:25:27  shen
  60.  * add in locks
  61.  * 
  62.  * Revision 1.58  92/02/25  16:42:28  jonathan
  63.  * Added find_pointer_in_block using binary search from
  64.  * ses@techunix.technion.ac.il. (part of wais-8-b3-ses).
  65.  * 
  66.  * 
  67.  * Revision 1.57  92/02/25  12:49:16  jonathan
  68.  * removed a bunch of \n's from waislog's.
  69.  * 
  70.  * Revision 1.56  92/02/17  16:23:58  jonathan
  71.  * Modified build_catalog so it passes over the first entry (which seems to be
  72.  * empty).
  73.  * 
  74.  * Revision 1.55  92/02/17  12:37:34  jonathan
  75.  * Added code to build a catalog containing all headlines and DocID's for
  76.  * documents in the database.
  77.  * 
  78.  * Revision 1.54  92/02/16  09:50:49  jonathan
  79.  * plugged some memory leaks.  I bet there are more.
  80.  * 
  81.  * Revision 1.53  92/02/16  09:26:39  jonathan
  82.  * ask harry.
  83.  * 
  84.  * Revision 1.52  92/02/12  13:25:12  jonathan
  85.  * Added "$Log" so RCS will put the log message in the header
  86.  * 
  87.  */
  88. /* ==================== */
  89.  
  90. /* ==================== */
  91. /*     To Do list       
  92.  *
  93.  * Implement a filename hashtable so that we can test quickly when
  94.  *   a file has been indexed.
  95.  * Free up all memory when we can. 
  96.  * Implement logrithmic merging
  97.  * 
  98.  * change DOC_TAB_ENTRY_FILENAME_ID_SIZE to 4 This must be in version 9
  99.  * change DOC_TAB_ENTRY_HEADLINE_ID_SIZE to 4 This must be in version 9
  100.  * change DOC_TAB_ENTRY_NUM_LINES_SIZE to 4 This must be in version 9
  101.  * change MAX_WORD_LENGTH to 15 This must be in version 9
  102.  */
  103.  
  104. /* A specification for this is called ir-engine.text in microsoft word. */
  105.  
  106. #include <string.h> /* for memset() */
  107.  
  108. #include "cutil.h"
  109. #include "irfiles.h"
  110. #include "panic.h"
  111. #include "ustubs.h" /* for strstr */
  112. #include "futil.h"
  113. #include "sockets.h"
  114. #include "version.h"
  115. #include "irext.h"
  116. #include "irlex.h" /* for MAX_WORD_LENGTH */
  117.  
  118. #include "lock.h"
  119.  
  120. #define PRINT_AS_INDEXING false /* also defined in irtfiles.c and irhash.c */
  121.  
  122. /*      -------------------------------    */
  123. #define DOC_TAB_HEADER_SIZE 2
  124. #define DOC_TAB_MAXIMUM_ENTRIES 8192
  125. #define DOC_TAB_ENTRY_FILENAME_ID_SIZE 3
  126. #define DOC_TAB_ENTRY_START_CHAR_SIZE 4
  127. #define DOC_TAB_ENTRY_END_CHAR_SIZE 4
  128. #define DOC_TAB_ENTRY_HEADLINE_ID_SIZE 3
  129. #define DOC_TAB_ENTRY_DOC_LENGTH_SIZE 4
  130. #define DOC_TAB_ENTRY_NUM_LINES_SIZE 3
  131. #define DOC_TAB_ENTRY_DATE_SIZE 4
  132. #define DOC_TAB_ELEMENT_SIZE 25 /* sum of above sizes */
  133.  
  134. #define DICTIONARY_ENTRY_SIZE 29 /* sum of MAX_WORD_LENGTH, 1 ('\0'), 
  135.                     NEXT_INDEX_BLOCK_SIZE and
  136.                     NUMBER_OF_OCCURANCES_SIZE */
  137.  
  138.  
  139. #define FILENAME_TABLE_HEADER_SIZE 4
  140. #define HEADLINE_TABLE_HEADER_SIZE 4
  141.  
  142. #define FILE_WRITE_DATE_SIZE 4
  143. #define NUMBER_OF_OCCURANCES_SIZE 4
  144. #define DOCUMENT_SCORE_LIMIT_SIZE 1
  145. #define DOCUMENT_SCORE_LIMIT 255  /* this is computed from DOCUMENT_SCORE_LIMIT_SIZE */
  146.  
  147. #define TIME_WAIT_QUERY_END 5
  148. #define TIMEOUT_WAIT_QUERY_END 45
  149.  
  150. static char* temp_dictionary_filename _AP((char* destination, database* db));
  151.  
  152. static long current_lock_type = INVALID_LOCK;
  153.  
  154. /*============================
  155.   ===   Database support   ===
  156.   ============================*/
  157.  
  158.  
  159. /* looks up the total word count in an existing dictionary. */
  160. static boolean look_up_total_word_count _AP((database *db));
  161. static boolean look_up_total_word_count(db)
  162. database *db;
  163. {
  164.   long word_count;
  165.   long answer = look_up_word_in_dictionary(DICTIONARY_TOTAL_SIZE_WORD,
  166.                        &word_count, db);
  167.   if(answer == 0){
  168.     waislog(WLOG_HIGH, WLOG_ERROR,
  169.         "error finding total_word_count in dictionary %s\n", 
  170.         db->database_file);
  171.     disposeDatabase(db);
  172.     return(false);
  173.   }
  174.   else if(answer == 0){
  175.     waislog(WLOG_HIGH, WLOG_ERROR,"total_word_count not found in dictionary\n.This is either an error,or the database is old.");
  176.     db->total_word_count = word_count;
  177.   }
  178.   else{
  179.     db->total_word_count = word_count;
  180.   }
  181.   /* printf("Total Words in DB: %ld\n", db->total_word_count); */
  182.   return(true);
  183. }
  184.  
  185.  
  186. database*     
  187. openDatabase(name,initialize,for_search)
  188. char* name;
  189. boolean initialize;
  190. boolean for_search;
  191. {
  192.   /* open a database (open all its files), and return an opaque object.
  193.      return NULL if there is an error
  194.    */
  195.   unsigned long pid;
  196.   long timeout;
  197.   char file[MAX_FILE_NAME_LEN + 1 ];
  198.   char tmpfile[MAX_FILE_NAME_LEN + 1];
  199.   char open_mode[4];
  200.   database* db = (database*)s_malloc((size_t)sizeof(database));    
  201.   if (db == NULL){ 
  202.     waislog(WLOG_HIGH, WLOG_ERROR,
  203.         "can't make a database, out of memory.\n");
  204.     return(NULL);
  205.   }
  206.  
  207.   db->total_word_count = 0;
  208.  
  209.   if (for_search == true)  
  210.     strncpy(open_mode,"rb",3); /* read only for searching */
  211.   else 
  212.     strncpy(open_mode,"r+b",4); /* read/write for building */
  213.  
  214.   /* set the query parameter to the original name */
  215.   {
  216.     query_parameter_type parameters;
  217.     char **list;
  218.     list=(char **)s_malloc(2*sizeof(char*));
  219.     list[0]=s_strdup(name);
  220.     list[1]=NULL;
  221.     parameters.srcs = list;
  222.     set_query_parameter(SET_SELECT_SOURCE,¶meters);
  223.   }
  224.    
  225.   /* ask the backend where the database lives, but put in the 
  226.      directory information that we already have.  This changes
  227.      the 'name' variable. */
  228.   db->database_file = 
  229.     s_strdup(merge_pathnames(database_file(pathname_name(name)),
  230.                  pathname_directory(name, tmpfile)));
  231.   if (for_search == true) {
  232.   
  233.     /* check and set appropriate locks */
  234.  
  235.     if( utlk_using_lock(db->database_file, LOCK_UPDATE) ) {
  236.       waislog(WLOG_HIGH, WLOG_ERROR,
  237.        "can't search the database as an update is currently running");
  238.         return(NULL);
  239.       }
  240.     if ( utlk_set_lock(db->database_file, LOCK_QUERY) )
  241.        current_lock_type = LOCK_QUERY;
  242.     else
  243.       waislog(WLOG_LOW, WLOG_INFO, "query lock can't be set");
  244.  
  245.     }
  246.  
  247.   else {
  248.  
  249.     if( utlk_using_lock_and_get_pid(db->database_file, LOCK_INDEX, &pid) &&
  250.         (pid != getpid()) ) {
  251.       waislog(WLOG_HIGH, WLOG_ERROR,
  252.        "an indexing is currently running on the database. Try again later.");
  253.         return(NULL);
  254.       }
  255.     if (  utlk_set_lock(db->database_file, LOCK_INDEX) )
  256.        current_lock_type = LOCK_INDEX;
  257.     else
  258.       waislog(WLOG_LOW, WLOG_INFO, "index lock can't be set");
  259.     if ( initialize == true ) {
  260.      /* wait for current query finishing off */
  261.      timeout = 0;
  262.      while ( utlk_using_lock(db->database_file, LOCK_QUERY) ) {
  263.       if ( timeout >= TIMEOUT_WAIT_QUERY_END ) {
  264.          waislog(WLOG_HIGH, WLOG_ERROR,
  265.              "timed out in waiting for a query to finish. Try again later.");
  266.          utlk_unset_lock(db->database_file, LOCK_INDEX);
  267.          return(NULL);
  268.          }
  269.       waislog(WLOG_LOW, WLOG_INFO,
  270.              "waiting for a query to finish to initialize the database...");
  271.       sleep(TIME_WAIT_QUERY_END);
  272.       timeout += TIME_WAIT_QUERY_END;
  273.       }
  274.      if (  utlk_set_lock(db->database_file, LOCK_UPDATE) )
  275.        current_lock_type = LOCK_UPDATE;
  276.      else
  277.        waislog(WLOG_LOW, WLOG_INFO, "update lock can't be set");
  278.      }
  279.  
  280.     }
  281.   
  282.   if(initialize == true){
  283.     initialize_index_files(db);
  284.   } 
  285.   else     {
  286.     db->dictionary_stream = 
  287.       s_fopen(dictionary_filename(file, db),open_mode);
  288.     if (db->dictionary_stream == NULL){ 
  289.       waislog(WLOG_HIGH,WLOG_ERROR,"can't open the word hash file %s\n",file); 
  290.       disposeDatabase(db);
  291.       return(NULL);
  292.     }
  293.     /* find the total_word_count from the dictionary */
  294.     if(for_search){
  295.       if(false == look_up_total_word_count(db)) { /* side effects db */
  296.         disposeDatabase(db);
  297.     return(NULL);
  298.         }
  299.     }
  300.           
  301.     db->filename_table_stream =
  302.       s_fopen(filename_table_filename(file, db),open_mode);
  303.     if (db->filename_table_stream == NULL){ 
  304.       waislog(WLOG_HIGH, WLOG_ERROR,
  305.           "can't open the filename file %s", file); 
  306.       disposeDatabase(db);
  307.       return(NULL);
  308.     }
  309.         
  310.     db->headline_table_stream = 
  311.       s_fopen(headline_table_filename(file, db),open_mode);
  312.     if (db->headline_table_stream == NULL){
  313.       waislog(WLOG_HIGH, WLOG_ERROR,
  314.           "can't open the headline file %s", file); 
  315.       disposeDatabase(db);
  316.       return(NULL);
  317.     }
  318.     db->document_table_stream = 
  319.       s_fopen(document_table_filename(file, db),open_mode);
  320.     if (db->document_table_stream == NULL){ 
  321.       waislog(WLOG_HIGH, WLOG_ERROR,
  322.           "can't open the document id file %s", file); 
  323.       disposeDatabase(db);
  324.       return(NULL);
  325.     }
  326.       
  327.     /* initialize the allocated entries variable */
  328.     s_fseek(db->document_table_stream, 0L, SEEK_END);
  329.     db->doc_table_allocated_entries = 
  330.       (ftell(db->document_table_stream) - DOC_TAB_HEADER_SIZE) 
  331.     / DOC_TAB_ELEMENT_SIZE;
  332.   }
  333.   db->index_file_number = 0;
  334.   ext_open_database(db,initialize,for_search);
  335.   return(db);
  336. }
  337.  
  338.  
  339. void        
  340. closeDatabase(db)
  341. database* db;
  342. /* close a database and all its files. Do not dispose of the structure. */
  343. {
  344.   if (db == NULL)
  345.     return;
  346.   close_dictionary_file(db);
  347.   if (db->dictionary_stream != NULL)
  348.     s_fclose(db->dictionary_stream);
  349.   if (db->filename_table_stream != NULL)
  350.     s_fclose(db->filename_table_stream);
  351.   if (db->headline_table_stream != NULL)
  352.     s_fclose(db->headline_table_stream);
  353.   if (db->document_table_stream != NULL)
  354.     s_fclose(db->document_table_stream);
  355.   if (db->index_stream != NULL)
  356.     s_fclose(db->index_stream);
  357.   ext_close_database(db);
  358.   utlk_unset_lock(db->database_file, current_lock_type);
  359.   if ( current_lock_type == LOCK_UPDATE)
  360.      utlk_unset_lock(db->database_file, LOCK_INDEX);
  361.   current_lock_type = INVALID_LOCK;
  362.  
  363. }
  364.  
  365. void 
  366. disposeDatabase(db)
  367. database* db;
  368. {
  369.     closeDatabase(db);
  370.     s_free(db->database_file);
  371.     s_free(db);
  372. }
  373.  
  374. /* ==================================== */
  375. /* ===  Initialization of the files === */
  376. /* ==================================== */
  377.  
  378. #define BLOCK_SIZE 16384 /* size of blocks of zeros to write to a file */
  379.  
  380. static FILE* initialize_file _AP((long size,char* filename,boolean zero_it));
  381.  
  382. static FILE* initialize_file(size,filename,zero_it)
  383. long size;
  384. char* filename;
  385. boolean zero_it;
  386. /* initializes a file by opening a new stream, making it the right
  387.  * size and returning the stream.
  388.  */
  389. {
  390.   FILE* file = NULL;
  391.   long i;
  392.  
  393. #ifdef ANSI_LIKE
  394.   remove(filename);
  395. #endif
  396.  
  397.   file = s_fopen(filename, "wb");
  398.   if(NULL == file){ 
  399.     panic("The file %s could not be opened\n", filename);
  400.   }
  401.  
  402.   if(zero_it){
  403.     if(size >= BLOCK_SIZE){    /* then write big blocks of zeros */
  404.       char* zeros = NULL;
  405.       zeros = (char*)s_malloc((size_t)BLOCK_SIZE);
  406.       if(NULL == zeros){
  407.     panic("Could not allocate a large block of Zeros\n");
  408.       }
  409.       memset(zeros, 0, BLOCK_SIZE);
  410.       while(size >= BLOCK_SIZE){    
  411.     /* then write big blocks of zeros */
  412.     if(BLOCK_SIZE != fwrite(zeros, 1, BLOCK_SIZE, file))
  413.       panic("Write failed");
  414.     size = size - BLOCK_SIZE;
  415.       }
  416.       s_free(zeros);
  417.     }
  418.     for(i = 0; i < size; i++){    /* clean up the rest */
  419.       putc('\0', file); 
  420.     }
  421.   }    
  422.   else{                /* dont zero it */
  423.     grow_file(file, size);
  424.   }    
  425.  
  426. #ifdef THINK_C
  427.   /* set the mac file type to INDX */
  428.   setFileType(filename, WAIS_INDEX_FILE_TYPE, CREATOR);
  429. #endif                /* THINK_C */
  430.  
  431.   s_fclose(file);
  432.   file = s_fopen(filename, "r+b"); /* open it in read/write */
  433.   if(NULL == file){
  434.     panic("Error in initialization, can not reopen %s.\n", filename);
  435.   }
  436.   return(file);
  437. }
  438.  
  439. void initialize_index_files (db)
  440. database* db;
  441. /* This creates new index files, deleting any old ones. */
  442. {
  443.   char file[MAX_FILENAME_LEN];
  444.  
  445.   /* cprintf(PRINT_AS_INDEXING, "initializing index files: %s\n", db->database_file); */
  446.  
  447.   remove(dictionary_filename(file, db)); /* remove the old one */
  448.  
  449.   db->index_stream = NULL;
  450.  
  451.   db->doc_table_allocated_entries = 1; /* the 0th is the null pointer */
  452.   db->document_table_stream =
  453.     initialize_file((DOC_TAB_HEADER_SIZE + DOC_TAB_ELEMENT_SIZE),
  454.             document_table_filename(file, db), TRUE);
  455.   db->filename_table_stream =
  456.     initialize_file(FILENAME_TABLE_HEADER_SIZE,
  457.             filename_table_filename(file, db), TRUE);
  458.   db->headline_table_stream =
  459.     initialize_file(HEADLINE_TABLE_HEADER_SIZE,
  460.             headline_table_filename(file, db), TRUE);
  461. }
  462.  
  463. /* ========================= */
  464. /* ===  Dictionary File  === */
  465. /* ========================= */
  466.  
  467. /* The dictionary file is a 1 deep tree of blocks.  
  468.    The header of the file says how long the header block is.
  469.    The "header block" is a set of pointers to the heads of
  470.    the blocks in the dictionary.
  471.  
  472.    A dictionary block is a list of word and pointer pairs.  The words
  473.    are padded to a fixed length so that it is a fixed length record.
  474.    The pointers are pointers into the inverted file (except in the header
  475.    block where they are pointers into the dictionary file).
  476. */
  477.  
  478. /*  SEARCHING DICTIONARY FILES */
  479.    
  480. /* top level function:
  481.    long look_up_word_in_dictionary(char *word, long *word_id, database* db) 
  482.  */
  483.  
  484. unsigned char *dictionary_header_block = NULL; /* the dictionary header. 
  485.                           loaded once */
  486.  
  487. long number_of_dictionary_blocks = 0;  /* also the length of the dictionary 
  488.                       header block */
  489.  
  490. unsigned char *dictionary_block = NULL; /* this is one of the dict blocks */
  491.  
  492. int dictionary_last_word_occurances; /* This is a temporary hack so I can 
  493.                     separate out the relevance feedback 
  494.                     changes for posting. DON'T USE THIS 
  495.                     ANYWHERE - IT'LL BE GONE SOON
  496.                       */
  497.  
  498. void close_dictionary_file(db)
  499.      database *db;
  500. {
  501.   if(dictionary_header_block) s_free(dictionary_header_block);
  502.   dictionary_header_block = NULL;
  503. }
  504.   
  505.   
  506. static long fread_from_stream _AP((FILE* stream,unsigned char* buf,
  507.                    long nbytes));
  508.  
  509. static long fread_from_stream(stream,buf,nbytes)
  510. FILE *stream;
  511. unsigned char *buf;
  512. long nbytes;
  513. /* this is a safe version of unix 'fread' it does all the checking
  514.  * and looping necessary
  515.  */
  516. {
  517.   long didRead;
  518.   long toRead = nbytes;
  519.   long totalRead = 0;        /* paranoia */
  520.   /*printf("in Fread_from_stream buffer %ld, nbytes %ld\n", (long)buf, nbytes); */
  521.  
  522.   while (toRead > 0){
  523.     didRead = fread(buf, sizeof(char), toRead, stream);
  524.     if(didRead == -1)        /* error*/
  525.       return(-1);
  526.     if(didRead == 0)        /* eof */
  527.       return(-2);        /* maybe this should return 0? */
  528.     toRead -= didRead;
  529.     buf += didRead;
  530.     totalRead += didRead;
  531.   }
  532.   if(totalRead != nbytes)    /* we overread for some reason */
  533.     return(- totalRead);    /* bad news */    
  534.   return(totalRead);
  535. }
  536.  
  537. #ifdef DICT_FUNC
  538. char *dictionary_block_word(i,block)
  539. long i;
  540. unsigned char *block;
  541. /* returns the word field in the ith dictionary block entry */
  542. {
  543.   return((char *)(block + (i * DICTIONARY_ENTRY_SIZE)));
  544. }
  545.  
  546. long dictionary_block_position(i,block)
  547. long i;
  548. unsigned char *block;
  549. /* returns the position field in the ith dictionary block entry */
  550. {
  551.   /* printf("dictionary_block_position %ld\n",
  552.      read_bytes_from_memory
  553.      (NEXT_INDEX_BLOCK_SIZE,
  554.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  555.       MAX_WORD_LENGTH + 1)); */
  556.   return(read_bytes_from_memory
  557.      (NEXT_INDEX_BLOCK_SIZE,
  558.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  559.       MAX_WORD_LENGTH + 1));
  560. }
  561.  
  562. long dictionary_block_word_occurances(i,block)
  563. long i;
  564. unsigned char *block;
  565. /* returns the occurances field in the ith dictionary block entry */
  566. {
  567.   return(read_bytes_from_memory
  568.      (NEXT_INDEX_BLOCK_SIZE,
  569.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  570.       MAX_WORD_LENGTH + 1 + NEXT_INDEX_BLOCK_SIZE));
  571. }
  572. #endif
  573.  
  574. static long find_pointer_in_block _AP((char* word,unsigned char* block,
  575.                        long block_length,
  576.                        long *position
  577.                        ));
  578.  
  579. /* Courtesy of Simon Spero <ses@techunix.technion.ac.il> */
  580.  
  581. static long find_pointer_in_block(word,block,block_length, position)
  582. char *word;
  583. unsigned char *block;
  584. long block_length; /* in entries */ 
  585. long *position;
  586. /* returns 0 if an error or if the word is below the lowest block,
  587.    (this confusion between error and NULL is bad, but found late in the 
  588.    design process)
  589.    it returns the positive position if the word is there exactly,
  590.    and the negative of the position of the word before it if the
  591.    word is not there exactly.
  592.    position is set with the entry postion in the block that the word was 
  593.    found.  This is used for searching.
  594. */
  595. {
  596.   /* find the entry in the dictionary header for this word.
  597.      returns 0 if not found. */
  598.   /* this could be binary search XXX */
  599.   long i,high,low,tmp;
  600.   low = 0;
  601.   high = block_length;
  602.   i = (low+high)/2;
  603.     while(low != high) {
  604.     long compare;
  605.     char *dictionary_word = dictionary_block_word(i, block);
  606. /*
  607.     printf("dw = %s, w = %s, low = %d, i = %d, hi = %d\n",
  608.        dictionary_word,word,low,i,high);
  609. */
  610.     if(dictionary_word[0] == '\0') {
  611.       if(high != i) {
  612.     high = i;
  613.     i = (low+i)/2;
  614.       } else {
  615.     *position = i-1;
  616.     return(- dictionary_block_position(i-1,block));
  617.       }
  618.     } else {
  619.       compare = strcmp(dictionary_word, word);
  620.       if(0 == compare) {
  621.     dictionary_last_word_occurances = 
  622.       dictionary_block_word_occurances(i,block);
  623.     *position = i;
  624.     return(dictionary_block_position(i, block));
  625.       }
  626.       if(compare > 0){
  627.     if(high != i) {
  628.       high = i;
  629.       i = (low+i)/2;
  630.       } else {
  631.         *position = i-1;
  632.         return(- dictionary_block_position(i-1 , block));
  633.       }
  634.       } else {
  635.     if (low != i) {
  636.       low = i;
  637.       i = (0.5+high+i)/2;
  638.     } else {
  639.       *position = i;
  640.       return(- dictionary_block_position(i , block));
  641.     }
  642.       }
  643.     }
  644.   }
  645.   if(i == 0) {
  646.     *position = 0;
  647.     return(0);
  648.   }
  649.   else {
  650.     *position = i-1;
  651.     return(- dictionary_block_position(i - 1, block));
  652.   }
  653. }
  654.  
  655. unsigned char *read_dictionary_block(block,position,length,stream)
  656. unsigned char *block;
  657. long position;
  658. long length;
  659. FILE *stream;
  660. /* reads the dictionary block from the disk and returns it.
  661.    block is the place to put it, if it is NULL, then it is malloc'ed.
  662.    position is the position in the dictionary file to start reading.
  663.    length is th enumber of entries (not bytes) in the block.
  664.    stream is the dictionary stream.
  665.    
  666.    it returns NULL if it loses.
  667.  */
  668.     
  669. {
  670.   static long last_position = -1;
  671.   static unsigned char* last_block = NULL;
  672.   static FILE* last_dict_file = NULL; /* there may be more than one dict */
  673.  
  674.   if (stream != last_dict_file)
  675.    { /* invalidate the cache */
  676.      last_position = -1;
  677.      last_dict_file = stream;
  678.    }
  679.   
  680.   if(NULL == block)
  681.     block = (unsigned char *)s_malloc((size_t)(length*DICTIONARY_ENTRY_SIZE));
  682.  
  683.   if ((block != last_block) || 
  684.       (position != last_position)) {
  685.     last_position = position;
  686.     last_block = block;
  687.     s_fseek(stream, position, SEEK_SET);
  688.     if(0 > fread_from_stream(stream, block, (length * DICTIONARY_ENTRY_SIZE))){
  689.       waislog(WLOG_HIGH, WLOG_ERROR,
  690.           "Could not read the dictionary block %ld, length %ld",
  691.           block, length);
  692.       return(NULL);
  693.     }
  694.   }
  695.   return(block);
  696. }
  697.  
  698. long 
  699. look_up_word_in_dictionary(word, number_of_occurances, db)
  700. char *word;
  701. long *number_of_occurances;
  702. database* db;
  703. /* looks up the word in the dictionary file. Returns the pointer
  704.    into the inverted file or negative number if not found, 
  705.    or 0 if error.
  706.     It sets number_of_occurances (if it is not NULL) to the number
  707.       registered in the file.  This is used during searching.  
  708.       It is set to 0 if error or word not found.
  709.       If it is NULL, then it is not touched.
  710.  */
  711. {
  712.   long position;
  713.   long answer;
  714.   FILE *stream = db->dictionary_stream;
  715.   long dictionary_block_pos;
  716.  
  717.   if(NULL == dictionary_header_block)
  718.    {
  719.      s_fseek(stream, 0L, SEEK_SET);
  720.      number_of_dictionary_blocks = read_bytes(DICTIONARY_HEADER_SIZE,stream);
  721.      dictionary_header_block =
  722.        read_dictionary_block(dictionary_header_block,DICTIONARY_HEADER_SIZE,
  723.                  number_of_dictionary_blocks,stream);
  724.      if(NULL == dictionary_header_block)
  725.       {    waislog(WLOG_HIGH, WLOG_ERROR,
  726.         "Could not read dictionary header block in db %s.",
  727.         db->database_file);
  728.     return(0);
  729.       }
  730.    }
  731.  
  732.   dictionary_block_pos = 
  733.     find_pointer_in_block(word,
  734.                dictionary_header_block,
  735.                number_of_dictionary_blocks,
  736.                &position);
  737.   if(0 == dictionary_block_pos)
  738.    { /* waislog(WLOG_HIGH, WLOG_ERROR, "Could not find pointer for word '%s' (location %ld) in block in db %s!",
  739.          word, word, db->database_file); */
  740.      return(-1);  /* not an error, necessarily if the word is before the first entry */
  741.    }
  742.  
  743.   dictionary_block = 
  744.     read_dictionary_block(dictionary_block,ABS(dictionary_block_pos),
  745.               DICTIONARY_BLOCK_SIZE,stream);
  746.   if(NULL == dictionary_block)
  747.    { waislog(WLOG_HIGH, WLOG_ERROR,
  748.          "Could not read dictionary block %ld in db %s",
  749.          ABS(dictionary_block_pos),
  750.          db->database_file);
  751.      return(0);
  752.    }
  753.   answer = find_pointer_in_block(word, dictionary_block, 
  754.                  DICTIONARY_BLOCK_SIZE, &position);
  755.   if((NULL != number_of_occurances)) {
  756.     if (answer > 0)
  757.     *number_of_occurances = 
  758.       dictionary_block_word_occurances(position, dictionary_block);
  759.     else
  760.       *number_of_occurances = 0;
  761.   }
  762.  
  763.   return(answer);
  764. }
  765.  
  766.  
  767. /*  BUILDING DICTIONARY FILES */
  768.  
  769.  
  770. long number_of_dictionary_entries; /* number allocated */
  771.  
  772. char *block_of_zeros = NULL;
  773.  
  774. static void write_zeros_to_stream _AP((long n_bytes,FILE* stream));
  775.  
  776. static void write_zeros_to_stream(n_bytes,stream)
  777. long n_bytes;
  778. FILE *stream;
  779. /* writes zeros to a file quickly */
  780. {    
  781.   long i;
  782.   if(n_bytes >= BLOCK_SIZE){    /* then write big blocks of zeros */
  783.     if(NULL == block_of_zeros){
  784.       block_of_zeros = (char*)s_malloc((size_t)BLOCK_SIZE);
  785.       memset(block_of_zeros, 0, BLOCK_SIZE);
  786.     }
  787.     while(n_bytes >= BLOCK_SIZE){    
  788.       /* then write big blocks of zeros */
  789.       if(BLOCK_SIZE != 
  790.      fwrite(block_of_zeros, sizeof(char), BLOCK_SIZE, stream))
  791.     panic("Write failed");
  792.       n_bytes -= BLOCK_SIZE;
  793.     }
  794.   }
  795.   for(i = 0; i < n_bytes; i++){    /* clean up the rest */
  796.     putc('\0', stream); 
  797.   }
  798. }    
  799.  
  800. /* returns 0 if successful */
  801. long init_dict_file_for_writing(db)
  802. database *db;
  803. {
  804.   char filename[MAX_FILENAME_LEN];
  805.  
  806.   if (db->dictionary_stream != NULL)
  807.     fclose(db->dictionary_stream);
  808.   db->dictionary_stream = 
  809.     s_fopen(temp_dictionary_filename(filename, db), "w+b");
  810.  
  811.   db->total_word_count = 0;
  812.   init_dict_file_detailed(db->dictionary_stream,db->number_of_words);
  813.   return(0);
  814. }
  815.  
  816. static long dict_number_of_blocks _AP((long number_of_words));
  817.  
  818. static long
  819. dict_number_of_blocks(number_of_words)
  820. long number_of_words;
  821. {
  822.   long number_of_blocks;
  823.   number_of_blocks = (number_of_words / DICTIONARY_BLOCK_SIZE) +
  824.     ((0 == (number_of_words % DICTIONARY_BLOCK_SIZE)) ? 0 : 1);
  825.   return(number_of_blocks);
  826. }
  827.  
  828. void
  829. record_num_blocks_in_dict(dictionary_stream,number_of_words)
  830. FILE* dictionary_stream;
  831. long number_of_words;
  832. { /* write the number of blocks */
  833.   s_fseek(dictionary_stream, 0L, SEEK_SET);
  834.   write_bytes(dict_number_of_blocks(number_of_words),
  835.           DICTIONARY_HEADER_SIZE, dictionary_stream);
  836.   fseek(dictionary_stream, 0L, SEEK_END);
  837. }
  838.  
  839. void
  840. init_dict_file_detailed(dictionary_stream,number_of_words)
  841. FILE* dictionary_stream;
  842. long number_of_words;
  843. {
  844.   /* create space for the table in the front of the file */
  845.   write_zeros_to_stream(DICTIONARY_HEADER_SIZE + 
  846.             (DICTIONARY_ENTRY_SIZE * 
  847.              dict_number_of_blocks(number_of_words)),
  848.             dictionary_stream);
  849.   record_num_blocks_in_dict(dictionary_stream,number_of_words);
  850.   number_of_dictionary_entries = 0;
  851. }
  852.  
  853. /* this must be called in alphabetical order, and writes the word to
  854.    the dictionary file. */    
  855. long add_word_to_dictionary(word,position,number_of_occurances,db)
  856. char *word;
  857. long position;
  858. long number_of_occurances;
  859. database *db;
  860.      /* Puts a word into the dictionary file. */
  861. {
  862.   /* assumes the stream has been initialized, and it is positioned
  863.      at the end */
  864.   FILE *stream = db->dictionary_stream;
  865.   char padded_word[MAX_WORD_LENGTH + 1];
  866.  
  867.   memset(padded_word, 0, MAX_WORD_LENGTH + 1); /* clear the word */
  868.   strcpy(padded_word, word);
  869.  
  870.   if(0 == (number_of_dictionary_entries % DICTIONARY_BLOCK_SIZE)){
  871.     /* then add an entry in the header */
  872.     long original_position = s_ftell(stream);
  873.     long header_entry = number_of_dictionary_entries / DICTIONARY_BLOCK_SIZE; 
  874.     /* printf("Adding header entry %ld %s original pos %ld\n", 
  875.        header_entry, padded_word, original_position); */
  876.     fseek(stream, DICTIONARY_HEADER_SIZE + 
  877.       (header_entry * DICTIONARY_ENTRY_SIZE), SEEK_SET);
  878.     if((MAX_WORD_LENGTH + 1) != 
  879.        fwrite(padded_word, sizeof(char), MAX_WORD_LENGTH + 1, stream))
  880.       panic("Write failed");
  881.     write_bytes(original_position, NEXT_INDEX_BLOCK_SIZE, stream);
  882.     write_bytes(0L, NUMBER_OF_OCCURANCES_SIZE, stream);
  883.     fseek(stream, original_position, SEEK_SET); /* go back to the end */
  884.     /* zero the next block */
  885.     write_zeros_to_stream(DICTIONARY_ENTRY_SIZE * DICTIONARY_BLOCK_SIZE,
  886.               stream); 
  887.     fseek(stream, original_position, SEEK_SET);      
  888.   }
  889.   /* write the word */    
  890.   if((MAX_WORD_LENGTH + 1) !=
  891.      fwrite(padded_word, sizeof(char), MAX_WORD_LENGTH + 1, stream))
  892.     panic("Write failed");
  893.   write_bytes(position, NEXT_INDEX_BLOCK_SIZE, stream);
  894.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, stream);
  895.   number_of_dictionary_entries++;    
  896.   db->total_word_count += number_of_occurances;
  897.   return(0);
  898. }
  899.  
  900. /* this is called after all add_words are done, but before the file 
  901.    is closed. Returns 0 if successful. */
  902. long
  903. finished_add_word_to_dictionary(db)
  904.      database* db;
  905. {
  906.   char temp_filename[MAX_FILENAME_LEN];
  907.   char filename[MAX_FILENAME_LEN];
  908.  
  909.   waislog(WLOG_LOW, WLOG_INFO, "Total word count for dictionary is: %ld",
  910.       db->total_word_count);
  911.   if(0 != add_word_to_dictionary(DICTIONARY_TOTAL_SIZE_WORD, 
  912.                 1, db->total_word_count, db))
  913.     return(-1);
  914.  
  915.   record_num_blocks_in_dict(db->dictionary_stream,db->number_of_words);
  916.  
  917.   fflush(db->dictionary_stream); /* so that any new opens will see a 
  918.                     valid file */
  919.  
  920.   /* rename the .dcttmp file to dct */
  921.   temp_dictionary_filename(temp_filename, db);
  922.   dictionary_filename(filename, db);
  923.   /* printf("renaming %s to %s\n", temp_filename, filename); */
  924.   if(0 != rename(temp_filename, filename))
  925.     waislog(WLOG_HIGH, WLOG_ERROR,
  926.         "could not rename file %s to %s",
  927.         temp_filename, filename);
  928.   return(0);
  929. }
  930.   
  931. void print_dictionary_block(block,size)
  932. unsigned char *block;
  933. long size;
  934. /* this prints the contents of a dictionary block */
  935. {
  936.   long i;
  937.   for(i = 0; i < size; i++){
  938.     char *word = dictionary_block_word(i, block);
  939.     if(word[0] == '\0')
  940.       break;
  941.     /* I assume this is only for debugging - JG */
  942.     printf("Entry %3ld: %21s %7ld %7ld\n", i, word,
  943.         dictionary_block_position(i, block),
  944.         dictionary_block_word_occurances(i, block));
  945.   }
  946. }
  947.  
  948. void print_dictionary _AP((database* db));
  949.   
  950. void print_dictionary(db)
  951. database *db;
  952. {
  953.   /* prints the contents of a dictionary */
  954.   FILE *stream = db->dictionary_stream;
  955.   long i;
  956.   long new_number_of_dictionary_blocks;
  957.  
  958.   if(NULL == stream)
  959.     panic("dictionary stream is not open");
  960.   s_fseek(stream, 0L, SEEK_SET);
  961.   new_number_of_dictionary_blocks = read_bytes(DICTIONARY_HEADER_SIZE, stream);
  962.   if(new_number_of_dictionary_blocks > number_of_dictionary_blocks)
  963.     dictionary_header_block = NULL;
  964.   number_of_dictionary_blocks = new_number_of_dictionary_blocks;
  965.   printf("Number of dictionary blocks %ld\n", number_of_dictionary_blocks);
  966.   if(NULL == (dictionary_header_block =
  967.           read_dictionary_block(dictionary_header_block,
  968.                     DICTIONARY_HEADER_SIZE,
  969.                     number_of_dictionary_blocks,
  970.                     stream)))
  971.     panic("Could not read dictionary header block");
  972.   printf("The Dictionary Header Block:\n");
  973.   print_dictionary_block(dictionary_header_block, number_of_dictionary_blocks);
  974.   for(i = 0; i < number_of_dictionary_blocks; i++){
  975.     long pos = dictionary_block_position(i, dictionary_header_block);
  976.     if(NULL == (dictionary_block =
  977.         read_dictionary_block(dictionary_block,
  978.                       pos, DICTIONARY_BLOCK_SIZE, stream)))
  979.       panic("Could not read dictionary block %ld", pos);
  980.     printf("\n\nDictionary block %ld (position %ld):\n", i, pos);
  981.     print_dictionary_block(dictionary_block, DICTIONARY_BLOCK_SIZE);
  982.   }
  983.   fseek(stream, 0L, SEEK_END);
  984. }
  985.  
  986. #ifdef testing
  987. /* dictionary testing code */
  988.  
  989. static void check_dictionary_entry _AP((char* word,long expected_position,
  990.                     database* db));
  991.  
  992. static void check_dictionary_entry(word,expected_position,db)
  993. char *word;
  994. long expected_position;
  995. database *db;
  996. {
  997.   if(expected_position != look_up_word_in_dictionary(word, NULL, db)) {
  998.     waislog(WLOG_HIGH, WLOG_ERROR,
  999.         "%s should be %ld is %ld in db %s", 
  1000.         word, expected_position, 
  1001.         look_up_word_in_dictionary(word, NULL, db),
  1002.         db->database_file);
  1003.   }
  1004. }
  1005.   
  1006. static void test_dictionary _AP((database* db));
  1007.  
  1008. static void test_dictionary(db)
  1009. database *db;
  1010. /* this is just an trivial test */
  1011. {
  1012.  
  1013.   db->number_of_words = 3;
  1014.   init_dict_file_for_writing(db);
  1015.   add_word_to_dictionary("aardvark", 123L, 0l, db);
  1016.   add_word_to_dictionary("house", 234L, 0L, db);
  1017.   add_word_to_dictionary("mary", 345L, 0L, db);
  1018.   fflush(db->dictionary_stream);
  1019.   print_dictionary(db);
  1020.   check_dictionary_entry("aardvark", 123L, db);
  1021.   check_dictionary_entry("house", 234L, db);
  1022.   check_dictionary_entry("mary", 345L, db);
  1023.   check_dictionary_entry("food", -123L, db);
  1024.   check_dictionary_entry("zebra", -345L, db);
  1025.   check_dictionary_entry("aaarf", 0L, db);
  1026. }
  1027. #endif /* def testing */
  1028.  
  1029.  
  1030. /*========================*
  1031.  *===  Document Table  ===*
  1032.  *========================*/
  1033.  
  1034. boolean
  1035. read_document_table_entry(doc_entry,number,db)
  1036. document_table_entry* doc_entry;
  1037. long number;
  1038. database* db;
  1039. /* returns a document_table_entry on the stack */
  1040. {
  1041.   long position;
  1042.   FILE *stream = db->document_table_stream;
  1043.     
  1044.   position = (DOC_TAB_HEADER_SIZE + 
  1045.           ((long)number * (long)DOC_TAB_ELEMENT_SIZE));
  1046.  
  1047.   if (0 != fseek(stream, position, SEEK_SET))
  1048.     { 
  1049.       waislog(WLOG_HIGH, WLOG_ERROR,
  1050.           "fseek failed into the document table to position %ld in db %s", 
  1051.           position,
  1052.           db->database_file);
  1053.       return(false);
  1054.     }
  1055.     
  1056.   doc_entry->filename_id = read_bytes(DOC_TAB_ENTRY_FILENAME_ID_SIZE, 
  1057.                      stream);
  1058.   doc_entry->headline_id = read_bytes(DOC_TAB_ENTRY_HEADLINE_ID_SIZE, 
  1059.                      stream);    
  1060.   doc_entry->start_character = 
  1061.     read_bytes(DOC_TAB_ENTRY_START_CHAR_SIZE, stream);
  1062.   doc_entry->end_character = 
  1063.     read_bytes(DOC_TAB_ENTRY_END_CHAR_SIZE, stream);
  1064.   doc_entry->document_length = 
  1065.     read_bytes(DOC_TAB_ENTRY_DOC_LENGTH_SIZE, stream);
  1066.   doc_entry->number_of_lines = 
  1067.     read_bytes(DOC_TAB_ENTRY_NUM_LINES_SIZE, stream);
  1068.   doc_entry->date = 
  1069.     read_bytes(DOC_TAB_ENTRY_DATE_SIZE, stream);
  1070.   if (doc_entry->date == EOF) { 
  1071.     return(false);
  1072.   }
  1073.  
  1074. /*printf("read_document_table_entry pos %ld val %lx\n",position,doc_entry->date);*/
  1075.  
  1076.   return(true);
  1077. }
  1078.  
  1079.  
  1080. boolean
  1081. writeUserValToDocIDTable(userVal,doc,db)
  1082. unsigned long userVal;
  1083. long doc;
  1084. database* db;
  1085. /* the docIDTable needs to keep a user value for use by other indexing
  1086.    systems.  Currently it is stuffed in the date field. 
  1087.  
  1088.    This routine needs to be updated if read_document_table_entry changes
  1089.  */
  1090. {
  1091.   long position;
  1092.   
  1093.   position = (DOC_TAB_HEADER_SIZE +
  1094.           ((long)doc * (long)DOC_TAB_ELEMENT_SIZE) +
  1095.           DOC_TAB_ENTRY_FILENAME_ID_SIZE +
  1096.           DOC_TAB_ENTRY_HEADLINE_ID_SIZE + 
  1097.           DOC_TAB_ENTRY_START_CHAR_SIZE +
  1098.           DOC_TAB_ENTRY_END_CHAR_SIZE +
  1099.           DOC_TAB_ENTRY_DOC_LENGTH_SIZE +
  1100.           DOC_TAB_ENTRY_NUM_LINES_SIZE);
  1101.  
  1102.   if (0 != fseek(db->document_table_stream,position,SEEK_SET))
  1103.    { waislog(WLOG_HIGH, WLOG_ERROR,
  1104.          "fseek failed into the document table to position %ld in db %s", 
  1105.          position,db->database_file);
  1106.      return(false);
  1107.    }
  1108.  
  1109. /*printf("writeUserValToDocIDTable pos %ld val %lx\n",position,userVal);*/
  1110.  
  1111.   write_bytes(userVal,DOC_TAB_ENTRY_DATE_SIZE,db->document_table_stream);
  1112.   fflush(db->document_table_stream);
  1113.   return(true);
  1114. }
  1115.  
  1116.  
  1117.  
  1118. #ifdef testing
  1119.  
  1120. static  boolean check_document_id _AP((long doc_id,database* db));
  1121.  
  1122. static  boolean
  1123. check_document_id(doc_id,db)
  1124. long doc_id;
  1125. database* db;
  1126. /* returns true if that is a valid doc_id (corresponds to a file
  1127.    that has not been deleted */
  1128. {
  1129.   long position;
  1130.   FILE *stream = db->document_table_stream;
  1131.   long filename_id;
  1132.   char filename[MAX_FILE_NAME_LEN];
  1133.  
  1134.   position = (DOC_TAB_HEADER_SIZE + 
  1135.           ((long)doc_id * (long)DOC_TAB_ELEMENT_SIZE));
  1136.  
  1137.   if (0 != fseek(stream, position, SEEK_SET)) { 
  1138.     waislog(WLOG_HIGH, WLOG_ERROR,
  1139.         "fseek failed into the document table to position %ld in db %s",
  1140.         position,
  1141.         db->database_file);
  1142.     return(false);
  1143.   }
  1144.     
  1145.   filename_id = read_bytes(DOC_TAB_ENTRY_FILENAME_ID_SIZE, stream);
  1146.   /* probe the file.  Is there a faster way? */
  1147.   return(probe_file_possibly_compressed
  1148.      (read_filename_table_entry(filename_id, filename,NULL,db)));
  1149. }
  1150. #endif
  1151.  
  1152. long write_document_table_entry(doc_table_entry, db)
  1153. document_table_entry* doc_table_entry;
  1154. database* db;
  1155. {
  1156.   /* returns the document_id */
  1157.   s_fseek(db->document_table_stream,
  1158.          (DOC_TAB_HEADER_SIZE +
  1159.           (db->doc_table_allocated_entries *
  1160.            DOC_TAB_ELEMENT_SIZE)),
  1161.          SEEK_SET);
  1162.   /* write the pieces */
  1163.   write_bytes(doc_table_entry->filename_id,
  1164.           DOC_TAB_ENTRY_FILENAME_ID_SIZE,
  1165.           db->document_table_stream);
  1166.   write_bytes(doc_table_entry->headline_id,
  1167.           DOC_TAB_ENTRY_HEADLINE_ID_SIZE,
  1168.           db->document_table_stream);
  1169.   write_bytes(doc_table_entry->start_character,
  1170.           DOC_TAB_ENTRY_START_CHAR_SIZE,
  1171.           db->document_table_stream);
  1172.   write_bytes(doc_table_entry->end_character,
  1173.           DOC_TAB_ENTRY_END_CHAR_SIZE,
  1174.           db->document_table_stream);
  1175.   write_bytes(doc_table_entry->document_length,
  1176.           DOC_TAB_ENTRY_DOC_LENGTH_SIZE,
  1177.           db->document_table_stream);
  1178.   /*  printf("Writing %ld lines\n", document_table_entry->number_of_lines); */
  1179.   write_bytes(doc_table_entry->number_of_lines,
  1180.           DOC_TAB_ENTRY_NUM_LINES_SIZE,
  1181.           db->document_table_stream);
  1182.   write_bytes(doc_table_entry->date,
  1183.           DOC_TAB_ENTRY_DATE_SIZE,
  1184.           db->document_table_stream);
  1185.   db->doc_table_allocated_entries++;
  1186.   return(db->doc_table_allocated_entries);
  1187. }
  1188.  
  1189. long next_document_id(db)
  1190. database* db;
  1191. {
  1192.   return(db->doc_table_allocated_entries);
  1193. }
  1194.  
  1195.  
  1196. /*========================*
  1197.  *===  Filename table  ===*
  1198.  *========================*/
  1199.  
  1200. #ifndef MAXPATHLEN /* think_c does not define it for instance */
  1201. #define MAXPATHLEN 2000
  1202. #endif /* MAXPATHLEN */
  1203.  
  1204. static char *read_filename_table_stream _AP((long position, 
  1205.                         char* filename,
  1206.                         char* type, 
  1207.                         time_t* file_write_date,
  1208.                         FILE *stream));
  1209.  
  1210. static char *read_filename_table_stream(position,filename,type,
  1211.                     file_write_date, stream)
  1212. long position;
  1213. char* filename;
  1214. char* type;
  1215. time_t* file_write_date;
  1216. FILE *stream;
  1217. {
  1218.   /* Returns the filename array after side effecting it,
  1219.    *  or NULL if an error.
  1220.    * The type of the file is put in the argument "type".  This will
  1221.    * not be longer than MAX_FILE_NAME_LEN.
  1222.    *
  1223.    * if type is NULL then ignore it,
  1224.    * if file_write_date is NULL then ignore it,
  1225.    * If position is -1, then it does not seek.
  1226.    *
  1227.    * Leave the file positioned at the start of the next entry.
  1228.    */    
  1229.   long file_write_date_internal;
  1230.   char type_internal[MAX_TYPE_LEN];
  1231.  
  1232.   if(NULL == stream)
  1233.     return(NULL);
  1234.  
  1235.   if(NULL == type)  /* this means we do not care, so set up a dummy */
  1236.     type = type_internal;
  1237.  
  1238.   filename[0] = '\0';    /* init to the empty string */
  1239.   if(NULL != type)
  1240.     type[0] = '\0';    /* init to the empty string */
  1241.  
  1242.   if(position != -1){
  1243.     if (0 != fseek(stream, position, SEEK_SET)){
  1244.       waislog(WLOG_HIGH, WLOG_ERROR, "fseek failed into the filename index to position %ld", 
  1245.           position);
  1246.       return(NULL);
  1247.     }
  1248.   }
  1249.   if(false == read_string_from_file(stream, filename, MAX_FILE_NAME_LEN)){
  1250.     return(NULL);
  1251.   }
  1252.   else{
  1253.     file_write_date_internal = read_bytes(FILE_WRITE_DATE_SIZE, stream);
  1254.     if(file_write_date){
  1255.       *file_write_date = (time_t)file_write_date_internal;
  1256.     }
  1257.     if(false == read_string_from_file(stream, type, MAX_TYPE_LEN)){
  1258.       return(NULL);
  1259.     } 
  1260.   }
  1261.   return(filename);
  1262. }
  1263.      
  1264. char *read_filename_table_entry(position,filename,type,file_write_date,db)
  1265. long position;
  1266. char* filename;
  1267. char* type;
  1268. time_t* file_write_date;
  1269. database* db;
  1270. {
  1271.   /* Returns the filename array after side effecting it,
  1272.    *  or NULL if an error.
  1273.    * The type of the file is put in the argument "type".  This will
  1274.    * not be longer than MAX_FILE_NAME_LEN.
  1275.    *
  1276.    * if type is NULL then ignore it,
  1277.    * if file_write_date is NULL then ignore it,
  1278.    * If position is -1, then it does not seek.
  1279.    *
  1280.    * Leave the file positioned at the start of the next entry.
  1281.    */    
  1282.   FILE *stream = db->filename_table_stream;
  1283.   return(read_filename_table_stream(position,filename,type,
  1284.                     file_write_date,stream));
  1285. }
  1286.  
  1287. long write_filename_table_entry(filename,type,db)
  1288. char* filename;
  1289. char *type;
  1290. database* db;
  1291. {
  1292.   /* writes the filename (NULL terminated),
  1293.      followed by 4 bytes of creation date,
  1294.      followed by the file type (NULL terminated),
  1295.      Returns the postion of the filename
  1296.      */
  1297.   long free_position;
  1298.   char full_path[MAXPATHLEN];
  1299.   s_fseek(db->filename_table_stream, 0L, SEEK_END);
  1300.   free_position = ftell(db->filename_table_stream);
  1301.   /* add the filename to the hashtable not done yet XXX
  1302.      (setf (gethash filename *filename_table_hashtable*)
  1303.      (file_write_date filename))
  1304.      */
  1305.   fprintf(db->filename_table_stream, "%s", truename(filename, full_path));
  1306.   fputc(0, db->filename_table_stream);
  1307.   if(FILE_WRITE_DATE_SIZE != sizeof(time_t)){ /* check if these are the same */
  1308.     panic("We have a problem with the file_write_date_size\n");
  1309.   }
  1310.   write_bytes((long)file_write_date(filename),
  1311.               FILE_WRITE_DATE_SIZE, db->filename_table_stream);
  1312. /*  fwrite(type, sizeof(char), strlen(type) + 1, db->filename_table_stream);*/
  1313.   fprintf(db->filename_table_stream, "%s",type);
  1314.   fputc(0,db->filename_table_stream);
  1315.   return(free_position);
  1316. }
  1317.  
  1318. /* functions to figure out if the file is in the index already */
  1319.         
  1320. static boolean filename_in_filename_stream _AP((char *filename, char *type, 
  1321.                         time_t *file_write_date, 
  1322.                         FILE *stream));
  1323.              
  1324. static boolean filename_in_filename_stream(filename, type, 
  1325.                        file_write_date, stream)
  1326. char *filename;
  1327. char *type;
  1328. time_t *file_write_date;
  1329. FILE *stream;
  1330.      /* returns true if it is there (and side effects type and 
  1331.       file_write_date). 
  1332.         leaves the stream at the end of the file.
  1333.     If type or file_write_date is NULL, then it is a dont care.
  1334.     type, if it is an array, should be MAX_FILENAME_LEN long at least.
  1335.     */
  1336. {
  1337.   /* this is slow because it loops through the whole file every time.
  1338.      this might want to be optimized by making a hashtable. */
  1339.   char next_filename[MAX_FILENAME_LEN];
  1340.   
  1341.   s_fseek(stream, FILENAME_TABLE_HEADER_SIZE, SEEK_SET);
  1342.   while(!feof(stream)){
  1343.     char new_type[MAX_FILENAME_LEN];
  1344.     if(NULL == 
  1345.        read_filename_table_stream(-1, next_filename, new_type, 
  1346.                   file_write_date, stream))
  1347.       return(false);
  1348.     if(0 == strcmp(next_filename, filename))
  1349.       return(true);
  1350.   }
  1351. }
  1352.  
  1353. boolean filename_in_database(filename,type,file_write_date,db)
  1354. char *filename;
  1355. char *type;
  1356. time_t *file_write_date;
  1357. database *db;
  1358. {
  1359.   return(filename_in_filename_stream(filename, type, file_write_date, 
  1360.                      db->filename_table_stream));
  1361. }
  1362.  
  1363. /* this caches the last filename that was found to be in the filename file,
  1364.    this way repeated attempts to figure out if a file is there will be fast.
  1365.    This is the case when retrieving successive blocks of a file. */   
  1366. char last_filename_found_in_file[MAX_FILE_NAME_LEN];
  1367. char last_filename_file[MAX_FILE_NAME_LEN];
  1368.  
  1369. boolean filename_in_filename_file(filename,type,file_write_date, filename_file)
  1370. char *filename;
  1371. char *type;
  1372. time_t *file_write_date;
  1373. char *filename_file;
  1374. {
  1375.   if(NULL == filename)
  1376.     return(false);
  1377.   
  1378.   if(0 == strcmp(last_filename_found_in_file, filename) &&
  1379.      0 == strcmp(last_filename_file, filename_file))
  1380.     return(true);
  1381.   else
  1382.    { FILE *stream = s_fopen(filename_file, "r");
  1383.      boolean answer;
  1384.  
  1385.      if(NULL == stream)
  1386.       { s_fclose(stream);
  1387.     return(false);
  1388.       }
  1389.      answer = 
  1390.        filename_in_filename_stream(filename,type,file_write_date, stream);
  1391.      if(answer == true)
  1392.       { /* record it in the cache */
  1393.     strncpy(last_filename_file, filename_file, MAX_FILE_NAME_LEN);
  1394.     strncpy(last_filename_found_in_file, filename, MAX_FILE_NAME_LEN);
  1395.       }
  1396.      s_fclose(stream);
  1397.      return(answer);
  1398.    }
  1399. }
  1400.  
  1401.  
  1402. /*========================*
  1403.  *===  Headline Table  ===*
  1404.  *========================*/
  1405.  
  1406. char *read_headline_table_entry(position,db)
  1407. long position;
  1408. database* db;
  1409.   /* returns the headline array after side effecting it.  Beware that 
  1410.    * the next call to this function will overwrite the the headline_array
  1411.    */
  1412. {
  1413.   /* this is the headline that gets returned */
  1414.   static char headline_array[MAX_HEADLINE_LEN]; 
  1415.   FILE *stream = db->headline_table_stream;
  1416.   headline_array[0] = '\0';    /* init to the empty string */
  1417.     
  1418.   if (0 != fseek(stream, position, SEEK_SET)) { 
  1419.     waislog(WLOG_HIGH, WLOG_ERROR, 
  1420.         "fseek failed into the headline index to position %ld in db %s", 
  1421.         position, db->database_file);
  1422.     return(headline_array);
  1423.   }
  1424.   if(false == read_string_from_file(db->headline_table_stream, 
  1425.                     headline_array, MAX_FILE_NAME_LEN)){ 
  1426.    waislog(WLOG_HIGH, WLOG_ERROR, 
  1427.         "headline table is corrupt at %ld in db %s", 
  1428.         position, db->database_file);
  1429.   }
  1430.   return(headline_array);
  1431. }
  1432.  
  1433. /* writes the string to the file followed by a NULL.
  1434.  * The returned number is the position in the file to start reading.
  1435.  */
  1436. long write_headline_table_entry(headline,db)
  1437. char* headline;
  1438. database* db;
  1439. {
  1440.   /* writes the headline followed by a newline.
  1441.      Returns the postion of the headline.
  1442.      */
  1443.   long free_position;
  1444.   s_fseek(db->headline_table_stream, 0L, SEEK_END);
  1445.   free_position = ftell(db->headline_table_stream);
  1446.   /* printf("Headline position: %ld, next headline length: %ld\n",
  1447.      free_position, strlen(headline)); */
  1448.   fprintf(db->headline_table_stream, "%s", headline);
  1449.   fputc(0, db->headline_table_stream);
  1450.   return(free_position);
  1451. }
  1452.  
  1453.  
  1454. /* =================== */
  1455. /* === Source file === */
  1456. /* =================== */
  1457.  
  1458. /* the source file is an ascii file for describing a source.
  1459.    it is defined in ../doc/source.txt */
  1460.  
  1461. /* Registers the src structure with the directory of servers.
  1462.    Return true if successful */
  1463. boolean register_src_structure(filename)
  1464. char *filename;
  1465. {
  1466.   char string[200], *editor;
  1467.   long answer;
  1468.  
  1469.   if((editor = (char*)getenv("EDITOR")) == NULL &&
  1470.      (editor = (char*)getenv("VISUAL")) == NULL) {
  1471.     printf("Could not get EDITOR environment variable.\n"); 
  1472.     printf("Please check over the source structure: %s\n", filename);
  1473.     printf("Then mail it to wais-directory-of-servers@quake.think.com\n");
  1474.     return (false);
  1475.   }
  1476.  
  1477.   /* register the server with the directory of servers */
  1478.   printf("Please look over the Source description.  Be sure it contains\n");
  1479.   printf("an IP address and DNS name, as well as the port you intend\n");
  1480.   printf("to use for the server.\n");
  1481.   printf("When you are finished it will be mailed to the directory of servers.\n");
  1482.   fflush(stdout);
  1483.  
  1484.   sprintf(string, "exec %s %s", editor, filename);
  1485.   system(string);
  1486.  
  1487.   printf("Sending source struture to the directory of servers...");
  1488.  
  1489.   sprintf(string,
  1490.       "cat %s | mail wais-directory-of-servers@quake.think.com %s\n", 
  1491.       filename, getenv("USER"));
  1492.  
  1493.   answer = system(string);
  1494.   printf("Done.\n");      
  1495.   return((answer == 0)?true:false);
  1496. }
  1497.  
  1498.  
  1499. /* Writes a source structure to a file.
  1500.    If the export_database arg is set, then the tcp_port is used in the 
  1501.    tcp-port slot.
  1502.    Returns true if successful. */
  1503. boolean write_src_structure(filename, database_name, typename, 
  1504.                 filenames, count, export_database, tcp_port)
  1505.      char *filename;
  1506.      char *database_name;
  1507.      char *typename;
  1508.      char **filenames;
  1509.      long count;
  1510.      boolean export_database;
  1511.      long tcp_port;
  1512. {
  1513.   long i;
  1514.   char hostname[120];
  1515.   struct hostent *h;
  1516.  
  1517. #ifndef THINK_C
  1518. #ifndef M_XENIX
  1519.  
  1520.   FILE *source_stream = s_fopen(filename, "w");
  1521.  
  1522.   fprintf(source_stream, "\n\n(:source \n");
  1523.   fprintf(source_stream, "   :version  3 \n");
  1524.   if(export_database){
  1525.     gethostname(hostname, 120);
  1526.     h = gethostbyname(hostname);
  1527.     if (h != NULL && 
  1528.     h->h_addr_list != NULL &&
  1529.     h->h_addr_list[0] != NULL) {
  1530.       fprintf(source_stream,
  1531.           "   :ip-address \"%d.%d.%d.%d\"\n",
  1532.           (unsigned char)h->h_addr_list[0][0], 
  1533.           (unsigned char)h->h_addr_list[0][1],
  1534.           (unsigned char)h->h_addr_list[0][2],
  1535.           (unsigned char)h->h_addr_list[0][3] );
  1536.     }
  1537.     fprintf(source_stream, "   :ip-name \"%s\"\n", hostname );
  1538.     fprintf(source_stream, "   :tcp-port %ld\n", tcp_port);
  1539.   }
  1540.   fprintf(source_stream, "   :database-name \"%s\"\n", database_name);
  1541.   fprintf(source_stream, "   :cost 0.00 \n");
  1542.   fprintf(source_stream, "   :cost-unit :free \n");
  1543.   fprintf(source_stream, "   :maintainer \"%s\"\n", 
  1544.       current_user_name());
  1545.   fprintf(source_stream, "   :description \"Server created with %s on %s by %s\n",
  1546.       VERSION, printable_time(), current_user_name());
  1547.   if(count > 0){
  1548.     fprintf(source_stream, "The files of type %s used in the index were:\n",
  1549.         typename);
  1550.     for(i = 0; i < count; i++){
  1551.       char full_path[MAX_FILENAME_LEN + 1];
  1552.       fprintf(source_stream, "   %s\n", truename(filenames[i], full_path));
  1553.     }
  1554.   }
  1555.   fprintf(source_stream, "\"\n");
  1556.   fprintf(source_stream, ")\n");      
  1557.   s_fclose(source_stream);
  1558.   
  1559. #endif /* ndef M_XENIX */
  1560. #endif /* ndef THINK_C */
  1561.  
  1562.   return(true);
  1563. }
  1564.  
  1565. boolean
  1566. build_catalog(db)
  1567. database* db;
  1568. {
  1569.   char catalog_name[MAX_FILENAME_LEN];
  1570.   document_table_entry doc_entry;
  1571.   char filename[MAX_FILE_NAME_LEN], type[100];
  1572.   FILE *catalog;
  1573.   long i;
  1574.  
  1575.   sprintf(catalog_name,"%s%s",db->database_file, catalog_ext);
  1576.   if((catalog = s_fopen(catalog_name, "w")) == NULL) {
  1577.     waislog(WLOG_HIGH, WLOG_ERROR, 
  1578.         "Unable to open catalog file for write: %s.", catalog_name);
  1579.     return(false);
  1580.   }
  1581.  
  1582.   fprintf(catalog, "Catalog for database: %s\n", db->database_file);
  1583.   fprintf(catalog, "Date: %s\n", printable_time());
  1584.  
  1585.   /* the first document is empty - JG */
  1586.  
  1587.   fprintf(catalog, "%ld total document%s\n\n",
  1588.       db->doc_table_allocated_entries-1,
  1589.       (db->doc_table_allocated_entries==2) ? "":"s");
  1590.   
  1591.   for(i=1; i<db->doc_table_allocated_entries; i++) {
  1592.     fprintf(catalog, "Document # %ld\n", i);
  1593.     if (read_document_table_entry(&doc_entry, i, db) 
  1594.     == true){
  1595.       char *hl;
  1596.       long hll;
  1597.       read_filename_table_entry(doc_entry.filename_id, 
  1598.                 filename,
  1599.                 type,
  1600.                 NULL,
  1601.                 db);
  1602.       hl = read_headline_table_entry(doc_entry.headline_id,db);
  1603.       hll = strlen(hl);
  1604.       fprintf(catalog, "Headline: %s", hl);
  1605.       if((hll== 0) || (hl[hll-1] != '\n')) fprintf(catalog,"\n");
  1606.  
  1607.       fprintf(catalog, "DocID: %d %d %s\n\n",
  1608.           doc_entry.start_character, doc_entry.end_character,
  1609.           filename);
  1610.     }
  1611.     else {
  1612.       fprintf(catalog, "Unable to read document table!\n\n");
  1613.     }
  1614.   }
  1615.   s_fclose(catalog);
  1616.   return(true);
  1617. }
  1618.  
  1619. /*****************************/
  1620. /***   Database support    ***/
  1621. /*****************************/
  1622.  
  1623. char* dictionary_filename(destination,db)
  1624. char* destination;
  1625. database* db;
  1626. {
  1627.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1628.   s_strncat(destination,dictionary_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1629.   return(destination);
  1630. }
  1631.  
  1632. /* for use in building so that the real one does not get overstomped */
  1633. static char* temp_dictionary_filename(destination,db)
  1634. char* destination;
  1635. database* db;
  1636. {
  1637.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1638.   s_strncat(destination,dictionary_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1639.   s_strncat(destination,"tmp",MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1640.   return(destination);
  1641. }
  1642.  
  1643. char* document_table_filename(destination,db)
  1644. char* destination;
  1645. database* db;
  1646. {
  1647.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1648.   s_strncat(destination,document_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1649.   return(destination);
  1650. }
  1651.  
  1652. char* filename_table_filename(destination,db)
  1653. char* destination;
  1654. database* db;
  1655. {
  1656.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1657.   s_strncat(destination,filename_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1658.   return(destination);
  1659. }
  1660.  
  1661. char* headline_table_filename(destination,db)
  1662. char* destination;
  1663. database* db;
  1664. {
  1665.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1666.     s_strncat(destination,headline_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1667.     return(destination);
  1668. }
  1669. char* index_filename(destination,db)
  1670. char* destination;
  1671. database* db;
  1672. {
  1673.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1674.     s_strncat(destination,index_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1675.     return(destination);
  1676. }
  1677.  
  1678. /* this is used during index creation.  if the version is -2, then it means
  1679.    the real index_filename.  This is a kludge */
  1680. char* index_filename_with_version(version,destination,db)
  1681. long version;
  1682. char* destination;
  1683. database* db;
  1684. {
  1685.   if(version == -2L){
  1686.     return(index_filename(destination, db));
  1687.   }
  1688.   else{
  1689.     sprintf(destination, "%s%s%ld", db->database_file,
  1690.         index_ext, version);
  1691.     return(destination);
  1692.   }
  1693. }
  1694.  
  1695.  
  1696. char* source_filename(destination,db)
  1697. char* destination;
  1698. database* db;
  1699. {
  1700.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1701.   s_strncat(destination,source_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1702.   return(destination);
  1703. }
  1704.  
  1705. char*
  1706. get_doc(destination, document_id, db, headline)
  1707. char* destination;
  1708. long document_id;
  1709. database* db;
  1710. boolean headline;
  1711. {
  1712.   document_table_entry doc_entry;
  1713.   char filename[MAX_FILE_NAME_LEN], type[100];
  1714.   char *hl;
  1715.  
  1716.   if (read_document_table_entry(&doc_entry, document_id, db) 
  1717.       == true){
  1718.     read_filename_table_entry(doc_entry.filename_id, 
  1719.                   filename,
  1720.                   type,
  1721.                   NULL,
  1722.                   db);
  1723.     if (headline == TRUE) {
  1724.       hl = read_headline_table_entry(doc_entry.headline_id,db);
  1725.       sprintf(destination, "%d %d %s, \"%s\"", 
  1726.           doc_entry.start_character, doc_entry.end_character,
  1727.           filename, hl);
  1728.     }
  1729.     else
  1730.       sprintf(destination, "%d %d %s", 
  1731.           doc_entry.start_character, doc_entry.end_character,
  1732.           filename);
  1733.     return(s_strdup(type));
  1734.   }
  1735.   else return NULL;
  1736. }
  1737.  
  1738. long next_doc(destination, docID, db)
  1739. char* destination;
  1740. char* docID;
  1741. database* db;
  1742. {
  1743.   long i, start, end;
  1744.   char doc[MAX_FILE_NAME_LEN+50], fn[MAX_FILE_NAME_LEN];
  1745.   char *type, *loc;
  1746.  
  1747.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1748.     if ((type = get_doc(doc, i, db, FALSE)) != NULL) {
  1749.       s_free(type);
  1750.       if (strcmp(doc, docID) == 0) {
  1751.     type = get_doc(doc, i+1, db, TRUE);
  1752.     sscanf(doc, "%d %d %s", &start, &end, fn);
  1753.     if((loc = strstr(doc, ",")) == NULL) return -1;
  1754.     fn[loc-doc] = 0;
  1755.     sprintf(destination, "%s, %s", doc, type);
  1756.     s_free(type);
  1757.     if( end != 0)
  1758.       return(end-start);
  1759.     else {    
  1760.       /* whole file, find file length from the file */
  1761.       long size;
  1762.       FILE* file = NULL;
  1763.       if (((file = s_fopen(fn, "r")) != NULL) &&
  1764.           (fseek(file, 0L, SEEK_END) == 0)  &&
  1765.           ((size = ftell(file)) != -1)) {
  1766.         s_fclose(file);
  1767.         return(size);    /* we are done, bytes is set */
  1768.       }
  1769.       else {
  1770.         s_fclose(file);
  1771.         return(-1);        /* something went wrong with the file */
  1772.       }
  1773.     }
  1774.       }
  1775.     }
  1776.   }
  1777.   return -1;
  1778. }
  1779.  
  1780. long previous_doc(destination, docID, db)
  1781. char* destination;
  1782. char* docID;
  1783. database* db;
  1784. {
  1785.   long i, start, end;
  1786.   char doc[MAX_FILE_NAME_LEN+50], fn[MAX_FILE_NAME_LEN];
  1787.   char *type, *loc;
  1788.  
  1789.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1790.     if ((type = get_doc(doc, i, db, FALSE)) != NULL) {
  1791.       s_free(type);
  1792.       if (strcmp(doc, docID) == 0) {
  1793.     if (i != 0) {
  1794.       type = get_doc(doc, i-1, db, TRUE);
  1795.       sscanf(doc, "%d %d %s", &start, &end, fn);
  1796.       if((loc = strstr(doc, ",")) == NULL) return -1;
  1797.       fn[loc-doc] = 0;
  1798.       sprintf(destination, "%s, %s", doc, type);
  1799.       s_free(type);
  1800.       if( end != 0)
  1801.         return(end-start);
  1802.       else {    
  1803.         /* whole file, find file length from the file */
  1804.         long size;
  1805.         FILE* file = NULL;
  1806.         if (((file = s_fopen(fn, "r")) != NULL) &&
  1807.         (fseek(file, 0L, SEEK_END) == 0)  &&
  1808.         ((size = ftell(file)) != -1)) {
  1809.           s_fclose(file);
  1810.           return(size);    /* we are done, bytes is set */
  1811.         }
  1812.         else {
  1813.           s_fclose(file);
  1814.           return(-1);    /* something went wrong with the file */
  1815.         }
  1816.       }
  1817.     }
  1818.       }
  1819.     }
  1820.   }
  1821.   return(-1);
  1822. }
  1823.  
  1824. long next_docid(docID, db)
  1825. char* docID;
  1826. database* db;
  1827. {
  1828.   long i;
  1829.   char doc[MAX_FILE_NAME_LEN+50];
  1830.  
  1831.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1832.     if (get_doc(doc, i, db, FALSE) != NULL) {
  1833.       if (strcmp(doc, docID) == 0) {
  1834.     return (i+1);
  1835.       }
  1836.     }
  1837.   }
  1838.   return -1;
  1839. }
  1840.  
  1841. long previous_docid(docID, db)
  1842. char* docID;
  1843. database* db;
  1844. {
  1845.   long i;
  1846.   char doc[MAX_FILE_NAME_LEN+50];
  1847.  
  1848.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1849.     if (get_doc(doc, i, db, FALSE) != NULL) {
  1850.       if (strcmp(doc, docID) == 0) {
  1851.     return (i-1);
  1852.       }
  1853.     }
  1854.   }
  1855.   return -1;
  1856. }
  1857.  
  1858.